/*
 * Monkey-Moore - A simple and powerful relative search tool
 * Copyright (C) 2007 Ricardo J. Ricken (Darkl0rd)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "monkey_frame.hpp"
#include "monkey_about.hpp"
#include "monkey_options.hpp"
#include "monkey_table.hpp"
#include "monkey_thread.hpp"

#include <wx/file.h>
#include <wx/listctrl.h>
#include <wx/tokenzr.h>

#include <algorithm>
#include <utility>
#include <string>

using namespace std;

#if !defined(__WXMSW__) && !defined(__WXPM__)
#include "../resources/mmoore.xpm"
#endif

BEGIN_EVENT_TABLE(MonkeyFrame, wxFrame)
   EVT_BUTTON(MonkeyMoore_Browse, MonkeyFrame::OnBrowse)
   EVT_RADIOBUTTON(MonkeyMoore_RelativeSearch, MonkeyFrame::OnSearchType)
   EVT_RADIOBUTTON(MonkeyMoore_ValueScanSearch, MonkeyFrame::OnSearchType)
   EVT_BUTTON(MonkeyMoore_Search, MonkeyFrame::OnSearch)
   EVT_CHECKBOX(MonkeyMoore_UseWC, MonkeyFrame::OnUseWC)
   EVT_BUTTON(MonkeyMoore_Advanced, MonkeyFrame::OnAdvanced)
   EVT_CHECKBOX(MonkeyMoore_EnableCP, MonkeyFrame::OnEnableCP)
   EVT_BUTTON(MonkeyMoore_CharsetList, MonkeyFrame::OnCharsetList)
   EVT_CHECKBOX(MonkeyMoore_AllResults, MonkeyFrame::OnAllResults)
   EVT_BUTTON(MonkeyMoore_CreateTbl, MonkeyFrame::OnCreateTbl)
   EVT_BUTTON(MonkeyMoore_Clear, MonkeyFrame::OnClear)
   EVT_BUTTON(MonkeyMoore_Options, MonkeyFrame::OnOptions)
   EVT_BUTTON(MonkeyMoore_About, MonkeyFrame::OnAbout)
   EVT_BUTTON(MonkeyMoore_Cancel, MonkeyFrame::OnCancel)
   EVT_TEXT_ENTER(MonkeyMoore_KWord, MonkeyFrame::OnTextEnter)
   EVT_MENU(MonkeyThread_JobFinished, MonkeyFrame::OnJobFinished)
   EVT_MENU(MonkeyThread_JobAborted, MonkeyFrame::OnJobAborted)
   EVT_CLOSE(MonkeyFrame::OnClose)
END_EVENT_TABLE()


MonkeyFrame::MonkeyFrame (const wxString &title, MonkeyPrefs &mprefs, const wxPoint &pos, const wxSize &size) :
wxFrame(0, wxID_ANY, title, pos, size, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL), prefs(mprefs),
search_done(false), search_in_progress(false), search_was_aborted(false), advanced_shown(false)
{
   SetIcon(wxICON(mmoore));

   wxImage buttonset(wxT("images/buttons.png"), wxBITMAP_TYPE_PNG);
   const int num_images = buttonset.GetWidth() / 18;

   images = new wxImageList(18, 15, true, num_images);

   for (int i = 0; i < num_images; i++)
      images->Add(buttonset.GetSubImage(wxRect(i * 18, 0, 18, 15))); 

   // ---------------------------------
   // -- File -------------------------

   // -- input file textbox
   wxBoxSizer *fname_sz = new wxBoxSizer(wxVERTICAL);
   wxTextCtrl *fname = new wxTextCtrl(this, MonkeyMoore_FName);
   fname_sz->Add(fname, wxSizerFlags().Border(wxALL, 5).Expand());

   // -- file box
   wxStaticBoxSizer *filebox_sz = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("File")), wxHORIZONTAL);
   wxBitmapButton *browse = new wxBitmapButton(this, MonkeyMoore_Browse, images->GetBitmap(MonkeyBitmap_openfile));
   filebox_sz->Add(fname_sz, wxSizerFlags(1).Left().Expand());
   filebox_sz->Add(browse, wxSizerFlags().Right().Border(wxALL, 4));

   browse->SetBitmapDisabled(images->GetBitmap(MonkeyBitmap_openfile_grayed));
   
   // ---------------------------------
   // -- Search Parameters ------------

   // -- search type
   wxBoxSizer *searchtype_sz = new wxBoxSizer(wxHORIZONTAL);
   wxRadioButton *searchtype_rs = new wxRadioButton(this, MonkeyMoore_RelativeSearch, _(" Relative search"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
   wxRadioButton *searchtype_vsr = new wxRadioButton(this, MonkeyMoore_ValueScanSearch, _(" Value scan relative"));
   searchtype_sz->Add(searchtype_rs, wxSizerFlags().Left().Border(wxLEFT | wxRIGHT | wxTOP, 4));
   searchtype_sz->Add(searchtype_vsr, wxSizerFlags().Left().Border(wxLEFT | wxRIGHT | wxTOP, 4));

   // relative search is the default option
   searchtype_rs->SetValue(true);

   // -- search keyword textinput
   wxBoxSizer *searchctrl_sz = new wxBoxSizer(wxVERTICAL);
   wxTextCtrl *kword = new wxTextCtrl(this, MonkeyMoore_KWord, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
   searchctrl_sz->Add(kword, wxSizerFlags().Border(wxALL, 4).Expand());

   // -- search input row
   wxBoxSizer *searchinput_sz = new wxBoxSizer(wxHORIZONTAL);
   wxBitmapButton *search = new wxBitmapButton(this, MonkeyMoore_Search, images->GetBitmap(MonkeyBitmap_search));
   searchinput_sz->Add(searchctrl_sz, wxSizerFlags(1).Left().Expand());
   searchinput_sz->Add(search, wxSizerFlags().Right().Border(wxTOP | wxLEFT | wxRIGHT, 4));

   // sets a better bitmap for disabled state
   search->SetBitmapDisabled(images->GetBitmap(MonkeyBitmap_search_grayed));

   // -- search options (controls below the keyword input)
   wxBoxSizer *searchopt_sz = new wxBoxSizer(wxHORIZONTAL);
   wxBitmapButton *advanced = new wxBitmapButton(this, MonkeyMoore_Advanced, images->GetBitmap(MonkeyBitmap_advanced));
   wxCheckBox *use_wc = new wxCheckBox(this, MonkeyMoore_UseWC, _(" Enable Wildcards"));
   wxTextCtrl *wildcard = new wxTextCtrl(this, MonkeyMoore_Wildcard, wxEmptyString, wxDefaultPosition, wxSize(20, 18));
   searchopt_sz->Add(use_wc, wxSizerFlags().Border(wxALL, 4).Align(wxALIGN_CENTER_VERTICAL));
   searchopt_sz->Add(wildcard, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
   searchopt_sz->AddStretchSpacer(1);
   searchopt_sz->Add(advanced, wxSizerFlags().Right().Border(wxRIGHT, 4));

   wildcard->WriteText(wxT("*"));
   wildcard->Disable();

   // -- search box
   wxStaticBoxSizer *searchbox_sz = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Search Parameters")), wxVERTICAL);
   searchbox_sz->Add(searchtype_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT, 2));
   searchbox_sz->Add(searchinput_sz, wxSizerFlags().Border(wxTOP | wxLEFT, 2).Expand());
   searchbox_sz->Add(searchopt_sz, wxSizerFlags(1).Border(wxLEFT | wxBOTTOM, 2).Expand());

   // ---------------------------------
   // -- Advanced ---------------------

   // -- custom character pattern row
   wxBoxSizer *advancedopt_sz = new wxBoxSizer(wxHORIZONTAL);
   wxCheckBox *adv_enablepat = new wxCheckBox(this, MonkeyMoore_EnableCP, _(" Define character set:"));
   wxTextCtrl *char_pattern = new wxTextCtrl(this, MonkeyMoore_CharPattern);
   wxBitmapButton *charset_list = new wxBitmapButton(this, MonkeyMoore_CharsetList, images->GetBitmap(MonkeyBitmap_charset));
   advancedopt_sz->Add(adv_enablepat, wxSizerFlags().Border(wxRIGHT | wxTOP | wxLEFT, 4));
   advancedopt_sz->Add(char_pattern, wxSizerFlags(1).Expand().Border(wxRIGHT, 4));
   advancedopt_sz->Add(charset_list, wxSizerFlags().Right().Border(wxRIGHT, 4));

   // disabled at startup
   char_pattern->Disable();
   charset_list->Disable();
   charset_list->SetBitmapDisabled(images->GetBitmap(MonkeyBitmap_charset_grayed));

   // -- advanced box
   wxStaticBoxSizer *advancedbox_sz = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Advanced")), wxVERTICAL);
   advancedbox_sz->Add(advancedopt_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT, 2).Expand());

   // ---------------------------------
   // -- Results -------------------------

   // -- result list options
   wxBoxSizer *resultopt_sz = new wxBoxSizer(wxHORIZONTAL);
   wxButton *createtbl = new wxButton(this, MonkeyMoore_CreateTbl, _("&Create Table"));
   wxButton *clear = new wxButton(this, MonkeyMoore_Clear, _("&Clear List"));
   wxBitmapButton *options = new wxBitmapButton(this, MonkeyMoore_Options, images->GetBitmap(MonkeyBitmap_options));
   wxBitmapButton *about = new wxBitmapButton(this, MonkeyMoore_About, images->GetBitmap(MonkeyBitmap_about));
   wxStaticText *count_txt = new wxStaticText(this, wxID_ANY, _("Results: "));
   wxStaticText *counter = new wxStaticText(this, MonkeyMoore_Counter, wxT("0"), wxDefaultPosition, wxSize(30, 12), wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
   resultopt_sz->Add(createtbl, wxSizerFlags().Border(wxALL, 4).Left());
   resultopt_sz->Add(clear, wxSizerFlags().Border(wxALL, 4).Left());
   resultopt_sz->Add(options, wxSizerFlags().Border(wxALL, 4).Left());
   resultopt_sz->Add(about, wxSizerFlags().Border(wxALL, 4).Left());
   resultopt_sz->AddStretchSpacer(1);
   resultopt_sz->Add(count_txt, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
   resultopt_sz->Add(counter, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxRIGHT, 3));

   options->SetBitmapDisabled(images->GetBitmap(MonkeyBitmap_options_grayed));

   // result box
   wxStaticBoxSizer *resultbox_sz = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Results")), wxVERTICAL);
   wxListCtrl *results = new wxListCtrl(this, MonkeyMoore_Results, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
   wxCheckBox *show_all = new wxCheckBox(this, MonkeyMoore_AllResults, _(" Show repeated results"));
   resultbox_sz->Add(show_all, wxSizerFlags().Left().Border(wxLEFT, 5));
   resultbox_sz->Add(results, wxSizerFlags(1).Top().Border(wxALL, 4).Expand());
   resultbox_sz->Add(resultopt_sz, wxSizerFlags().Border(wxALL, 2).Expand());

   results->InsertColumn(0, _("Offset"));
   results->InsertColumn(1, _("Values"), wxLIST_FORMAT_LEFT, 90);
   results->InsertColumn(2, _("Preview"), wxLIST_FORMAT_LEFT, 200);

   // ---------------------------------
   // -- Search Progress --------------
   
   // -- widgets shown below the progress bar
   wxBoxSizer * progressinfo_sz = new wxBoxSizer(wxHORIZONTAL);
   wxStaticText *elapsed_time = new wxStaticText(this, MonkeyMoore_ElapsedTime, _("Searching..."));
   wxBitmapButton *cancel_search = new wxBitmapButton(this, MonkeyMoore_Cancel, images->GetBitmap(MonkeyBitmap_cancel));
   progressinfo_sz->Add(elapsed_time, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxTOP | wxLEFT, 4).Left().Expand());
   progressinfo_sz->AddStretchSpacer(1);
   progressinfo_sz->Add(cancel_search, wxSizerFlags().Right().Align(wxALIGN_CENTER_VERTICAL));

   // -- widgets shown while the search is in progress
   wxStaticBoxSizer *progress_sz = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Search Progress")), wxVERTICAL);
   wxGauge *progress = new wxGauge(this, MonkeyMoore_Progress, 100, wxDefaultPosition, wxSize(300, 12));
   progress_sz->Add(progress, wxSizerFlags().Border(wxALL, 4).Expand());
   progress_sz->Add(progressinfo_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT, 4).Expand());

   // ---------------------------------
   // -- Window layout ----------------

   wxBoxSizer *global_sizer = new wxBoxSizer(wxVERTICAL);
   global_sizer->Add(filebox_sz, wxSizerFlags().Top().Border(wxLEFT | wxRIGHT | wxTOP, 8).Expand());
   global_sizer->AddSpacer(5);
   global_sizer->Add(searchbox_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT, 8).Expand());
   global_sizer->AddSpacer(5);
   global_sizer->Add(advancedbox_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT, 8).Expand());
   global_sizer->AddSpacer(5);
   global_sizer->Add(resultbox_sz, wxSizerFlags(1).Border(wxLEFT | wxRIGHT, 8).Expand());
   global_sizer->AddSpacer(5);
   global_sizer->Add(progress_sz, wxSizerFlags().Border(wxLEFT | wxRIGHT | wxBOTTOM, 8).Expand());

   global_sizer->Show(progress_sz, false);
   global_sizer->Show(advancedbox_sz, false);

   // set tooltips for some widgets
   fname->SetToolTip(_("Path of the file where the\nsearch will be performed."));
   browse->SetToolTip(_("Search a file in your computer."));
   searchtype_rs->SetToolTip(_("Sets the search type\nto relative search."));
   searchtype_vsr->SetToolTip(_("Sets the search type\nto value scan relative."));
   kword->SetToolTip(_("Type the search keyword."));
   search->SetToolTip(_("Perform a search with\nthe specified parameters."));
   use_wc->SetToolTip(_("Allows the use of wildcards."));
   wildcard->SetToolTip(_("Type the desired wildcard\nin this field."));
   advanced->SetToolTip(_("Shows/hides the advanced panel."));
   adv_enablepat->SetToolTip(_("Allows the use of a\ncustom character set."));
   char_pattern->SetToolTip(_("Type the desired character\nset in this field."));
   charset_list->SetToolTip(_("Choose from a list of\ncommon character sets."));
   show_all->SetToolTip(_("Shows/hides duplicated results."));
   createtbl->SetToolTip(_("Build a table based on\nyour search results."));
   clear->SetToolTip(_("Clear results of the last search."));
   options->SetToolTip(_("Change application\noptions and settings."));
   about->SetToolTip(_("Displays more info\nabout Monkey-Moore."));
   counter->SetToolTip(_("Number of results found\nin the last search."));
   cancel_search->SetToolTip(_("Abort the current search."));

   SetSizer(global_sizer);
   browse->SetFocus();

   // restores previous UI state
   if (prefs.getBool(wxT("remember-ui-state")))
   {
      int stypeid = prefs.getBool(wxT("search-type"), wxT("rs")) ? MonkeyMoore_RelativeSearch : MonkeyMoore_ValueScanSearch;
      GetWindow <wxRadioButton *> (stypeid)->SetValue(true);

      OnSearchType(wxCommandEvent(wxEVT_COMMAND_RADIOBUTTON_SELECTED, stypeid));

      wildcard->SetValue(wxString::Format(wxT("%c"), prefs.get(wxT("wildcard")).GetChar(0)));
      show_all->SetValue(prefs.getBool(wxT("show-all-results")));

      if (prefs.getBool(wxT("advanced-shown")))
         OnAdvanced(wxCommandEvent());
   }

   vector <pair <wxString, wxString>> &charsets = prefs.getCommonCharsetList();
   const int start = MonkeyMoore_CharsetBase;
   const int end = MonkeyMoore_CharsetBase + charsets.size();

   // connects the common charsets menu entries dynamically to the correct event handler
   Connect(start, end, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MonkeyFrame::OnCharsetChosen));
}

/**
* Destructor. Frees allocated memory and resources.
*/
MonkeyFrame::~MonkeyFrame ()
{
   delete images;
}

/**
* Method called when the browse button is pressed.
* @param event not used
*/
void MonkeyFrame::OnBrowse (wxCommandEvent &WXUNUSED(event))
{
   const long flags = wxFD_OPEN | wxFD_FILE_MUST_EXIST;
   wxFileDialog dialog(this, MM_MSG_OPENFL, prefs.get(wxT("open-file-path")), wxT(""), _("All files (*.*)|*.*"), flags);

   if (dialog.ShowModal() == wxID_OK)
   {
      wxTextCtrl *fname = GetWindow <wxTextCtrl *> (MonkeyMoore_FName);
      
      if (!fname->IsEmpty()) fname->Clear();
      fname->WriteText(dialog.GetPath());

      prefs.set(wxT("open-file-path"), dialog.GetDirectory());
      FindWindowById(MonkeyMoore_KWord)->SetFocus();
   }
}

/**
* Method called a search type is chosen. Enables/disables the appropriate controls.
* @param event information regarding the event
*/
void MonkeyFrame::OnSearchType (wxCommandEvent &event)
{
   if (GetWindow <wxCheckBox *> (MonkeyMoore_UseWC)->IsChecked())
   {
      wxCommandEvent ev;
      ev.SetInt(event.GetId() == MonkeyMoore_RelativeSearch ? 1 : 0);

      OnUseWC(ev);
   }

   if (GetWindow <wxCheckBox *> (MonkeyMoore_EnableCP)->IsChecked())
   {
      wxCommandEvent ev;
      ev.SetInt(event.GetId() == MonkeyMoore_RelativeSearch ? 1 : 0);

      OnEnableCP(ev);
   }

   if (event.GetId() == MonkeyMoore_RelativeSearch)
   {
      FindWindowById(MonkeyMoore_UseWC)->Enable();
      FindWindowById(MonkeyMoore_EnableCP)->Enable();
   }
   else
   {
      FindWindowById(MonkeyMoore_UseWC)->Disable();
      FindWindowById(MonkeyMoore_EnableCP)->Disable();
   }
}

/**
* Method called when the search button is pressed.
* Validates the user input and init the search thread.
* @param event not used
*/
void MonkeyFrame::OnSearch (wxCommandEvent &WXUNUSED(event))
{
   wxString filename = GetWindow <wxTextCtrl *> (MonkeyMoore_FName)->GetValue();
   wxString keyword = GetWindow <wxTextCtrl *> (MonkeyMoore_KWord)->GetValue();

   if (!filename)
      return ShowWarning(MM_WARNING_NOFILE);

   wxChar card = 0;
   wxString charpattern = wxT("");
   vector <short> values;

   bool relative_search = GetWindow <wxRadioButton *> (MonkeyMoore_RelativeSearch)->GetValue();

   if (relative_search)
   {
      bool use_wildcards = GetWindow <wxCheckBox *> (MonkeyMoore_UseWC)->IsChecked();

      if (use_wildcards)
      {
         wxTextCtrl *wc = GetWindow <wxTextCtrl *> (MonkeyMoore_Wildcard);
         wxString wildcard = wc->GetValue();

         if (!wildcard)
            return ShowWarning(MM_WARNING_NOWC);

         if (wildcard.size() > 1)
            return ShowWarning(MM_WARNING_MANYWC);

          card = wildcard[0];
      }

      bool enable_cp = GetWindow <wxCheckBox *> (MonkeyMoore_EnableCP)->IsChecked();

      if (enable_cp)
      {
         wxTextCtrl *cp = GetWindow <wxTextCtrl *> (MonkeyMoore_CharPattern);
         charpattern = cp->GetValue();
      }

      // we need a valid keyword (when we use ascii)
      if (!CheckKeyword(keyword, card, charpattern)) return;
   }
   else
   {
      // search type is value scan relative

      wxStringTokenizer tkz(keyword, wxT(" "));

      for (int i = 0; tkz.HasMoreTokens(); i++)
      {
         wxString tk = tkz.GetNextToken();
         long value;
         
         if (!tk.ToLong(&value, 10))
            return ShowWarning(MM_WARNING_VSRINVALIDVAL);

         values.push_back(static_cast <short> (value));
      }
   }

   if (!wxFile::Exists(filename))
      return ShowWarning(MM_WARNING_FILENOTFOUND);

   if (!wxFile::Access(filename, wxFile::read))
      return ShowWarning(MM_WARNING_FILECANTACCESS);

   wxFile *file = new wxFile(filename, wxFile::read);

   if (!file->IsOpened())
      return ShowWarning(MM_WARNING_FILENOTFOUND);

   // prepares the search to be executed in another thread
   SearchThread *worker = 0;

   if (relative_search)
   {
      SearchParameters p(file, keyword, charpattern, card);
      worker = new SearchThread(p, last_results, mutex, prefs, this);
   }
   else
   {
      SearchParameters p(file, values);
      worker = new SearchThread(p, last_results, mutex, prefs, this);
   }

   if (worker->Create() == wxTHREAD_NO_ERROR)
   {
      wxBitmapButton *cancel_search = GetWindow <wxBitmapButton *> (MonkeyMoore_Cancel);
      cancel_search->SetBitmapLabel(images->GetBitmap(MonkeyBitmap_cancel));

      wxListCtrl *result_box = GetWindow <wxListCtrl *> (MonkeyMoore_Results);
      if (result_box->GetItemCount() != 0) result_box->DeleteAllItems();

      GetWindow <wxStaticText *> (MonkeyMoore_Counter)->SetLabel(wxT("0"));
      GetWindow <wxStaticText *> (MonkeyMoore_ElapsedTime)->SetLabel(_("Searching..."));

      SetCurrentProgress(0);
      ShowProgressBar();
      SetUIToBusy();
      chronometer.Start();
      last_results.clear();
      
      search_in_progress = true;
      worker->SetPriority(25);
      worker->Run();
   }
   else wxMessageBox(MM_WARNING_THREADERROR);
}

/**
* Method called when the checkbox which enables/disables the use of wildcards is toggled.
* @param event information regarding the event
*/
void MonkeyFrame::OnUseWC (wxCommandEvent &event)
{
   wxWindow *wildcard = FindWindowById(MonkeyMoore_Wildcard);
   event.IsChecked() ? wildcard->Enable() : wildcard->Disable();
}

/**
* Method called when the advanced options button is pressed.
* Shows/hides the advanced options box.
* @param event not used
*/
void MonkeyFrame::OnAdvanced(wxCommandEvent &WXUNUSED(event))
{
   wxBitmapButton *advanced = GetWindow <wxBitmapButton *> (MonkeyMoore_Advanced);
   advanced->SetBitmapLabel(images->GetBitmap(advanced_shown ? MonkeyBitmap_advanced : MonkeyBitmap_advanced_hide));

   wxSizer *advancedbox_sz = GetSizer()->GetItem(4)->GetSizer();
   wxASSERT(advancedbox_sz);

   advanced_shown = !advanced_shown;

   GetSizer()->Show(advancedbox_sz, advanced_shown);
   Layout(), Update();
}

/**
* Method called when the checkbox which enables the use of a custom character
* set is toggled. Enables/disables user defined character sets.
* @param event information regarding the event
*/
void MonkeyFrame::OnEnableCP (wxCommandEvent &event)
{
   wxWindow *charpattern = FindWindowById(MonkeyMoore_CharPattern);
   wxWindow *charset_list = FindWindowById(MonkeyMoore_CharsetList);
   
   if (event.IsChecked())
   {
      charpattern->Enable();
      charset_list->Enable();
   }
   else
   {
      charpattern->Disable();
      charset_list->Disable();
   }

   charpattern->SetFocus();
}

/**
* Method called when the common charsets buttons is pressed.
* Shows a popup menu containing the charsets found in the config file.
* @param event not used
*/
void MonkeyFrame::OnCharsetList (wxCommandEvent &WXUNUSED(event))
{
   vector <pair <wxString, wxString>> &charsets = prefs.getCommonCharsetList();
   vector <pair <wxString, wxString>>::iterator i;

   wxMenu charset_menu(wxT(""));
   int index = 0;

   for (i = charsets.begin(); i != charsets.end(); i++)
      charset_menu.Append(MonkeyMoore_CharsetBase + index++, i->first);

   PopupMenu(&charset_menu);
}

/**
* Method called when an item from the common charset popup menu is chosen.
* Replaces the contents of the character set text control with the chosen charset.
* @param event not used
*/
void MonkeyFrame::OnCharsetChosen (wxCommandEvent &event)
{
   vector <pair <wxString, wxString>> &charsets = prefs.getCommonCharsetList();
   wxString charset = charsets[event.GetId() - MonkeyMoore_CharsetBase].second;

   GetWindow <wxTextCtrl *> (MonkeyMoore_CharPattern)->SetValue(charset);
}

/**
* Method called when the checkbox which toggles the results display mode is pressed.
* @param event information regarding the event
*/
void MonkeyFrame::OnAllResults (wxCommandEvent &event)
{
   // if there's a search in progress, the change will only take effect when it's done
   if (!search_in_progress)
   {
      wxMutexLocker lock(mutex);

      if (event.IsChecked()) ShowResults(last_results);
      else ShowResults(remove_duplicates(last_results));
   }
}

/**
* This method is called when the create table button is pressed.
* @param event not used
*/
void MonkeyFrame::OnCreateTbl (wxCommandEvent &WXUNUSED(event))
{
   wxMutexLocker lock(mutex);
   wxListCtrl *result_box = GetWindow <wxListCtrl *> (MonkeyMoore_Results);
   int target = result_box->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);

   if (last_results.empty())
      return ShowWarning(MM_WARNING_TABLENORESULTS);

   if (target != wxNOT_FOUND)
   {
      wxListItem sel_item;
      sel_item.SetId(target);
      sel_item.SetMask(wxLIST_MASK_DATA);

      result_box->GetItem(sel_item);
      wxASSERT(sel_item.GetData() < last_results.size());

      MonkeyTable tbldiag(this, _("Create table file"), prefs, *images, wxSize(400, 340));

      GetWindow <wxCheckBox *> (MonkeyMoore_AllResults)->IsChecked() ?
         tbldiag.InitTableData(last_results.at(sel_item.GetData()).first.second) :
         tbldiag.InitTableData(remove_duplicates(last_results).at(sel_item.GetData()).first.second);

      tbldiag.ShowModal();
   }
   else ShowWarning(MM_WARNING_NORESULTSELECTED);
}

/**
* This method is called then the clear button is pressed.
* @param event not used
*/
void MonkeyFrame::OnClear (wxCommandEvent &WXUNUSED(event))
{
   wxMutexLocker lock(mutex);

   GetWindow <wxListCtrl *> (MonkeyMoore_Results)->DeleteAllItems();
   GetWindow <wxStaticText *> (MonkeyMoore_Counter)->SetLabel(wxT("0"));

   ShowProgressBar(false);
   SetCurrentProgress(0);

   search_done = false;
   last_results.clear();
}

void MonkeyFrame::OnOptions (wxCommandEvent &WXUNUSED(event))
{
   MonkeyOptions options(this, _("Settings"), prefs, wxSize(320, 280));
   options.ShowModal();

   // to be implemented...
}

/**
* This method is called when the about button is pressed.
* @param event not used
*/
void MonkeyFrame::OnAbout (wxCommandEvent &WXUNUSED(event))
{
   MonkeyAbout about(this, _("About Monkey-Moore"), wxSize(370, 300));
   about.ShowModal();
}

/**
* Method is called when the cancel button of the progress bar is pressed.
* @param event not used
*/
void MonkeyFrame::OnCancel (wxCommandEvent &WXUNUSED(event))
{
   if (search_in_progress) search_was_aborted = true;
   else ShowProgressBar(false);
}

/**
* This method is called when the ENTER key is pressed in the keyword
* input box, triggering the search button.
* @param event not used
*/
void MonkeyFrame::OnTextEnter (wxCommandEvent &WXUNUSED(event))
{
   wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, MonkeyMoore_Search);
   AddPendingEvent(event);
}

/**
* Method called when the window is about to be closed.
* @param event not used
*/
void MonkeyFrame::OnClose (wxCloseEvent &WXUNUSED(event))
{
   if (prefs.getBool(wxT("remember-ui-state")))
   {
      prefs.set(wxT("search-type"), GetWindow <wxRadioButton *> (MonkeyMoore_RelativeSearch)->GetValue() ? wxT("rs") : wxT("vsr"));
      prefs.set(wxT("wildcard"), GetWindow <wxTextCtrl *> (MonkeyMoore_Wildcard)->GetValue().substr(0, 1));
      prefs.setBool(wxT("advanced-shown"), advanced_shown);
      prefs.setBool(wxT("show-all-results"), GetWindow <wxCheckBox *> (MonkeyMoore_AllResults)->IsChecked());

      prefs.setBool(wxT("window-maximized"), IsMaximized());
   }

   if (!IsMaximized() && prefs.getBool(wxT("remember-size")))
      prefs.setSize(wxT("window-size-x"), wxT("window-size-y"), GetSize());

   if (!prefs.getBool(wxT("window-centered")) && prefs.getBool(wxT("remember-position")))
      prefs.setPoint(wxT("window-position-x"), wxT("window-position-y"), GetPosition());

   Destroy();
}

/**
* Enable/disable some widgets in order to put the user interface in a different state.
* @param busy true to set it to busy, false otherwise
*/
void MonkeyFrame::SetUIToBusy (const bool busy) const
{
   wxWindow *buttons[] = {
      GetWindow <wxWindow *> (MonkeyMoore_Browse),
      GetWindow <wxWindow *> (MonkeyMoore_Search),
      GetWindow <wxWindow *> (MonkeyMoore_CreateTbl),
      GetWindow <wxWindow *> (MonkeyMoore_Clear),
      GetWindow <wxWindow *> (MonkeyMoore_Options)
   };

   for (int i = 0; i < 5; i++)
      busy ? buttons[i]->Disable() : buttons[i]->Enable();
}

/**
* Shows/hides the progress bar. Also adjusts the window size appropriately.
* @param show true to show, false to hide
*/
void MonkeyFrame::ShowProgressBar (const bool show)
{
   wxSizer *progress_sz = GetSizer()->GetItem(8)->GetSizer();
   wxASSERT(progress_sz);

   bool is_shown = GetSizer()->IsShown(progress_sz);
   GetSizer()->Show(progress_sz, show);

   // screen size is changed only if:
   // 1) the progress bar wasn't visible and the user wants it to be;
   // 2) the progress bar was visible and the user doesn't want it to be anymore.
   if ((!is_shown && show) || (is_shown && !show))
   {
      if (!IsMaximized())
      {
         SetMinSize(wxSize(GetMinSize().x, GetMinSize().y + (show ? 73 : -73)));
         SetSize(wxSize(GetSize().x, GetSize().y + (show ? 73 : -73)));
      }

      Layout();
      Update();
   }
}

/**
* Checks whether the keyword is valid or not.
* @param kw keyword
* @param wc user-defined wildcard
* @return True if it's valid, false otherwise.
*/
bool MonkeyFrame::CheckKeyword (const wxString &kw, const wxChar wc, const wxString &cp)
{
   bool ascii_input = count_if(kw.begin(), kw.end(), bind2nd(greater <wxChar>(), 0x79)) == 0;
   bool custom_cp = cp.length() != 0;

   int n_wildcards = count(kw.begin(), kw.end(), wc);

   // we need 3 or more characters
   if (!kw || kw.size() < 3)
   {
      ShowWarning(MM_WARNING_KWORDSIZE);
      return false;
   }

   if (!custom_cp && ascii_input)
   {
      int n_lower = static_cast <int> (count_if(kw.begin(), kw.end(), is_lower));
      int n_upper = static_cast <int> (count_if(kw.begin(), kw.end(), is_upper));

      // we need 3 or more characters with the SAME capitalization
      if (n_lower && n_upper)
      {
         if (n_lower < 3 && n_upper < 3)
         {
            ShowWarning(MM_WARNING_KWORDCAPLETTERS);
            return false;
         }
      }
      else
      {
         int n_letters = static_cast <int> (count_if(kw.begin(), kw.end(), is_alpha()));

         // we still need 3 or more characters, not counting wildcards
         if (n_letters < 3)
         {
            ShowWarning(MM_WARNING_KWORDLETTERS);
            return false;
         }
      }

      // checks if we have only valid characters (letters and wildcards)
      if (count_if(kw.begin(), kw.end(), not1(is_alpha())) > n_wildcards)
      {
         ShowWarning(MM_WARNING_KWORDINVALIDCHARS);
         return false;
      }
   }
   else
   {
      // when not working with ASCII, we still have some restrictions

      if (kw.size() - n_wildcards < 3)
      {
         ShowWarning(MM_WARNING_KWORDNONWILDCARD);
         return false;
      }

      if (custom_cp)
      {
         if (n_wildcards && count(cp.begin(), cp.end(), wc))
         {
            ShowWarning(MM_WARNING_CHARPATWILDCARD);
            return false;
         }

         // For some reason, wxString don't work with some STL
         // algorithms (like sort), so plain std::wstring is used instead.
         std::wstring sorted_kw(kw.c_str()), sorted_cp(cp.c_str());

         sort(sorted_kw.begin(), sorted_kw.end());
         sort(sorted_cp.begin(), sorted_cp.end());

         if (unique(sorted_cp.begin(), sorted_cp.end()) != sorted_cp.end())
         {
            ShowWarning(MM_WARNING_CHARPATDUPLICATED);
            return false;
         }

         sorted_kw.erase(remove(sorted_kw.begin(), sorted_kw.end(), wc), sorted_kw.end());
         sorted_kw.erase(unique(sorted_kw.begin(), sorted_kw.end()), sorted_kw.end());

         if (!includes(sorted_cp.begin(), sorted_cp.end(), sorted_kw.begin(), sorted_kw.end()))
         {
            ShowWarning(MM_WARNING_KWORDCPMISMATCH);
            return false;
         }
      }
   }

   return true;
}

/**
* Display the search results.
* @param r search results
*/
void MonkeyFrame::ShowResults (const vector <result_type> &r)
{
   wxListCtrl *result_box = GetWindow <wxListCtrl *> (MonkeyMoore_Results);

   if (!r.empty())
   {
      if (result_box->GetItemCount() != 0)
         result_box->DeleteAllItems();

      for (vector<result_type>::size_type i = 0; i < r.size(); i++)
      {
         bool hex_offset = prefs.getBool(wxT("results-offset-mode"), wxT("hex"));
         wxString offset = wxString::Format(hex_offset ? wxT("0x%X") : wxT("%d"), r[i].first.first);

         result_box->InsertItem(i, offset);
         result_box->SetItemData(i, i);

         wxString values;
         const equivalency_type &ref = r[i].first.second;

         for (equivalency_type::const_iterator j = ref.begin(); j != ref.end(); j++)
            values += wxString::Format(wxT("%c=%02X "), (*j).first, (*j).second);

         result_box->SetItem(i, 1, values);
         result_box->SetItem(i, 2, r[i].second);
      }

      GetWindow <wxStaticText *> (MonkeyMoore_Counter)->SetLabel(wxString::Format(wxT("%d"), r.size()));
   }
}

/**
* Removes the duplicate search results.
* @param org searh results
* @return Vector containing unique search results.
*/
vector <result_type> MonkeyFrame::remove_duplicates (vector <result_type> &org)
{
   vector <equivalency_type> temp;
   vector <result_type> result;

   for (vector<result_type>::size_type i = 0; i < org.size(); i++)
   {
      equivalency_type &t = org[i].first.second;

      if (!count(temp.begin(), temp.end(), t))
      {
         temp.push_back(t);
         result.push_back(org[i]);
      }
   }
   return result;
}

/**
* This method is called when the worker thread completes the search.
* @param event not used
*/
void MonkeyFrame::OnJobFinished (wxCommandEvent &WXUNUSED(event))
{
   wxMutexLocker lock(mutex);

   search_done = true;
   search_in_progress = false;

   wxTimeSpan elapsed(0, 0, 0, chronometer.Time());
   wxString format = _("Elapsed time: ");
   
   format += elapsed.GetSeconds().ToLong() ?
      elapsed.GetMinutes() ? elapsed.Format(_("%M minute(s) and %S second(s).")) : elapsed.Format(_("%S second(s).")) :
      elapsed.Format(_("less than one second."));

   last_results.size() ?
      GetWindow <wxStaticText *> (MonkeyMoore_ElapsedTime)->SetLabel(format) :
      GetWindow <wxStaticText *> (MonkeyMoore_ElapsedTime)->SetLabel(_("No results found."));

   bool show_repeated = GetWindow <wxCheckBox *> (MonkeyMoore_AllResults)->IsChecked();
   ShowResults(!show_repeated ? remove_duplicates(last_results) : last_results);

   wxBitmapButton *cancel_search = GetWindow <wxBitmapButton *> (MonkeyMoore_Cancel);
   cancel_search->SetBitmapLabel(images->GetBitmap(MonkeyBitmap_done));

   SetUIToBusy(false);
}

/**
* This method is called when the worker thread aborts the search.
* @param event not used
*/
void MonkeyFrame::OnJobAborted (wxCommandEvent &WXUNUSED(event))
{
   search_in_progress = false;
   search_was_aborted = false;

   GetWindow <wxStaticText *> (MonkeyMoore_ElapsedTime)->SetLabel(_("Search was aborted."));
   last_results.clear();

   wxBitmapButton *cancel_search = GetWindow <wxBitmapButton *> (MonkeyMoore_Cancel);
   cancel_search->SetBitmapLabel(images->GetBitmap(MonkeyBitmap_done));

   SetCurrentProgress(0);
   SetUIToBusy(false);
}
